const express = require('express');
const router = express.Router();
const pool = require('../db');
const authMiddleware = require('../middleware/auth');
const { createLog } = require('../log');

// Helper to build tree
function buildTree(items) {
  const map = {};
  const roots = [];
  items.forEach(item => {
    item.children = [];
    map[item.id] = item;
  });
  items.forEach(item => {
    if (item.parent_id && map[item.parent_id]) {
      map[item.parent_id].children.push(item);
    } else {
      roots.push(item);
    }
  });
  // sort children by order_num if present (string compare keeps hierarchical codes like '0101' ordered)
  const sortFn = (a, b) => ((a.order_num || '')).localeCompare((b.order_num || ''));
  function sortRec(nodes) {
    nodes.sort(sortFn);
    nodes.forEach(n => sortRec(n.children));
  }
  sortRec(roots);
  return roots;
}

// GET /api/menus - return ALL menus (used by admin/menu list UI)
router.get('/menus', authMiddleware, async (req, res) => {
  try {
    // log page open
    createLog(req, { message: '打开菜单列表', tp: 'view' });

    const [rows] = await pool.query(`
      SELECT id, name, parent_id, order_num, url, icon, remarks, code, full_page, iframe_url, b_function
      FROM atb_menu
      ORDER BY IFNULL(parent_id, 0), order_num
    `);

    const tree = buildTree(rows.map(r => ({
      id: r.id,
      name: r.name,
      url: r.url,
      parent_id: r.parent_id,
      order_num: r.order_num,
      code: r.code,
      is_function: r.b_function,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks
    })));

    res.json({ menus: tree });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// GET /api/menus/authorized - return menus allowed for current user's role (used for login/navigation)
router.get('/menus/authorized', authMiddleware, async (req, res) => {
  try {
    const roleId = req.user.role_id;
    // fetch menus directly allowed for this role
    const [initialRows] = await pool.query(`
      SELECT m.id, m.name, m.parent_id, m.order_num, m.url, m.icon, m.remarks, m.code, m.full_page, m.iframe_url, m.b_function
      FROM atb_menu m
      JOIN atb_menu_role rm ON rm.menu_id = m.id
      WHERE rm.role_id = ?
      ORDER BY IFNULL(m.parent_id, 0), m.order_num
    `, [roleId]);

    // map by id for easy dedup
    const rowsById = {};
    (initialRows || []).forEach(r => { rowsById[r.id] = { ...r, mapped: true }; });

    // collect parent ids to fetch that are not already in rowsById (to include ancestors)
    let parentIdsToFetch = Array.from(new Set((initialRows || []).map(r => r.parent_id).filter(pid => pid && !rowsById[pid])));

    while (parentIdsToFetch.length > 0) {
      // fetch parent rows in batch
      const [parentRows] = await pool.query(`
        SELECT id, name, parent_id, order_num, url, icon, remarks, code, full_page, iframe_url, b_function
        FROM atb_menu WHERE id IN (?)
      `, [parentIdsToFetch]);

      // add each parent row to rowsById if not present
      parentRows.forEach(pr => {
        if (!rowsById[pr.id]) rowsById[pr.id] = { ...pr, mapped: false };
      });

      // compute next level of parent ids to fetch
      const nextParents = Array.from(new Set(parentRows.map(r => r.parent_id).filter(pid => pid && !rowsById[pid])));
      parentIdsToFetch = nextParents;
    }

    // Additionally, fetch descendants (children/grandchildren) of any included node so the frontend can render full branches
    // This ensures that when a role has permission to a deep child, the parent and that parent's other children can be displayed
    let childrenToFetch = Array.from(new Set(Object.keys(rowsById).map(id => parseInt(id, 10))));
    while (childrenToFetch.length > 0) {
      // fetch children in batch, but only include function items when the role has explicit mapping;
      // non-function (container) items are always allowed so branches can be displayed
      const [childRows] = await pool.query(`
        SELECT m.id, m.name, m.parent_id, m.order_num, m.url, m.icon, m.remarks, m.code, m.full_page, m.iframe_url, m.b_function,
               rm.role_id AS mapped_role_id
        FROM atb_menu m
        LEFT JOIN atb_menu_role rm ON rm.menu_id = m.id AND rm.role_id = ?
        WHERE m.parent_id IN (?) AND (m.b_function = 0 OR rm.role_id IS NOT NULL)
      `, [roleId, childrenToFetch]);

      // filter new children not already present
      const newChildren = (childRows || []).filter(cr => !rowsById[cr.id]);
      if (newChildren.length === 0) break;
      newChildren.forEach(nc => { rowsById[nc.id] = { ...nc, mapped: !!nc.mapped_role_id }; });
      // next round: fetch children of the newly added children
      childrenToFetch = newChildren.map(nc => nc.id);
    }

    // build final list from rowsById values and sort for stable order
    const allRows = Object.values(rowsById);
    allRows.sort((a, b) => {
      const pa = a.parent_id || 0;
      const pb = b.parent_id || 0;
      if (pa !== pb) return ('' + pa).localeCompare('' + pb, undefined, { numeric: true });
      return ((a.order_num || '')).localeCompare((b.order_num || ''));
    });

    const tree = buildTree(allRows.map(r => ({
      id: r.id,
      name: r.name,
      url: r.url,
      parent_id: r.parent_id,
      order_num: r.order_num,
      code: r.code,
      is_function: r.b_function,
      mapped: !!r.mapped,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks
    })));
    res.json({ menus: tree });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// GET /api/menus/:id/children - return direct children of a menu (filtered by role)
router.get('/menus/:id/children', authMiddleware, async (req, res) => {
  try {
    const roleId = req.user.role_id;
    const parentId = parseInt(req.params.id, 10);
    if (isNaN(parentId)) return res.status(400).json({ message: 'Invalid parent id' });  
    
    // fetch direct children that the role explicitly maps to (preserve original behavior)
    const [rows] = await pool.query(`
      SELECT m.id, m.name, m.parent_id, m.order_num, m.url, m.icon, m.remarks, m.code, m.full_page, m.iframe_url, m.b_function,
             rm.role_id AS mapped_role_id
      FROM atb_menu m
      LEFT JOIN atb_menu_role rm ON rm.menu_id = m.id AND rm.role_id = ?
      WHERE m.parent_id = ?  
      ORDER BY m.order_num
    `, [roleId, parentId]);

    // map by id for deduplication and easier merging
    const rowsById = {};
    (rows || []).forEach(r => { rowsById[r.id] = { ...r, mapped: !!r.mapped_role_id }; });

    // fetch descendants of any included node iteratively until no more are found
    let childrenToFetch = (rows || []).map(r => r.id);

    while (childrenToFetch.length > 0) {
      // fetch children in batch, but only include function items when the role has explicit mapping;
      // non-function (container) items are always allowed so branches can be displayed
      const [childRows] = await pool.query(`
        SELECT m.id, m.name, m.parent_id, m.order_num, m.url, m.icon, m.remarks, m.code, m.full_page, m.iframe_url, m.b_function,
               rm.role_id AS mapped_role_id
        FROM atb_menu m
        LEFT JOIN atb_menu_role rm ON rm.menu_id = m.id AND rm.role_id = ?
        WHERE m.parent_id IN (?) AND (m.b_function = 0 OR rm.role_id IS NOT NULL)
        ORDER BY m.order_num
      `, [roleId, childrenToFetch]);

      // filter new children not already present
      const newChildren = (childRows || []).filter(cr => !rowsById[cr.id]);
      if (newChildren.length === 0) break;
      newChildren.forEach(nc => { rowsById[nc.id] = { ...nc, mapped: !!nc.mapped_role_id }; });
      // next round: fetch children of the newly added children
      childrenToFetch = newChildren.map(nc => nc.id);
    }

    // build sorted array and tree
    const allRows = Object.values(rowsById);
    allRows.sort((a, b) => {
      const pa = a.parent_id || 0;
      const pb = b.parent_id || 0;
      if (pa !== pb) return ('' + pa).localeCompare('' + pb, undefined, { numeric: true });
      return ((a.order_num || '')).localeCompare((b.order_num || ''));
    });



    const tree = buildTree(allRows.map(r => ({
      id: r.id,
      name: r.name,
      url: r.url,
      parent_id: r.parent_id,
      order_num: r.order_num,
      code: r.code,
      is_function: r.b_function,
      mapped: !!r.mapped,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks
    })));

    res.json({ children: tree });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// GET /api/menus/children?parent_id=123 - alternative endpoint to return direct children (filtered by role)
router.get('/menus/children', authMiddleware, async (req, res) => {
  try {
    const roleId = req.user.role_id;
    const parentId = parseInt(req.query.parent_id, 10);
    if (isNaN(parentId)) return res.status(400).json({ message: 'Invalid parent id' });

    const [rows] = await pool.query(`
      SELECT m.id, m.name, m.parent_id, m.order_num, m.url, m.icon, m.remarks, m.code, m.full_page, m.iframe_url, m.b_function,
             rm.role_id AS mapped_role_id
      FROM atb_menu m
      LEFT JOIN atb_menu_role rm ON rm.menu_id = m.id AND rm.role_id = ?
      WHERE m.parent_id = ? AND (m.b_function = 0 OR rm.role_id IS NOT NULL)
      ORDER BY m.order_num
    `, [roleId, parentId]);

    const children = rows.map(r => ({
      id: r.id,
      name: r.name,
      url: r.url,
      parent_id: r.parent_id,
      order_num: r.order_num,
      code: r.code,
      is_function: r.b_function,
      mapped: !!r.mapped_role_id,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks,
      children: []
    }));

    res.json({ children });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// POST /api/menus - create a new menu
router.post('/menus', authMiddleware, async (req, res) => {
  try {
    const {
      name,
      parent_id = null,
      order_num = null,
      url = null,
      icon = null,
      remarks = null,
      code = null,
      full_page = 0,
      iframe_url = null,
      is_function = 0
    } = req.body || {};

    if (!name || typeof name !== 'string') {
      return res.status(400).json({ message: 'Invalid or missing name' });
    }

    const [result] = await pool.query(`
      INSERT INTO atb_menu (name, parent_id, order_num, url, icon, remarks, code, full_page, iframe_url, b_function)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `, [
      name,
      parent_id || null,
      order_num,
      url,
      icon,
      remarks,
      code,
      full_page ? 1 : 0,
      iframe_url,
      is_function ? 1 : 0
    ]);

    const insertedId = result.insertId;
    const [rows] = await pool.query(`
      SELECT id, name, parent_id, order_num, url, icon, remarks, code, full_page, iframe_url, b_function
      FROM atb_menu WHERE id = ?
    `, [insertedId]);

    if (!rows || rows.length === 0) {
      return res.status(500).json({ message: 'Failed to retrieve created menu' });
    }

    const r = rows[0];

    // log creation
    createLog(req, { message: `新增菜单 ${name}`, tp: 'create', obj_json: JSON.stringify({ id: r.id, name: r.name }) });

    res.status(201).json({ menu: {
      id: r.id,
      name: r.name,
      parent_id: r.parent_id,
      order_num: r.order_num,
      url: r.url,
      code: r.code,
      is_function: r.b_function,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks,
      children: []
    }});
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// PUT /api/menus/:id - update an existing menu
router.put('/menus/:id', authMiddleware, async (req, res) => {
  try {
    const id = parseInt(req.params.id, 10);
    if (isNaN(id)) return res.status(400).json({ message: 'Invalid menu id' });

    const allowed = [
      'name','parent_id','order_num','url','icon','remarks','code','full_page','iframe_url','is_function'
    ];

    const updates = [];
    const values = [];

    allowed.forEach(field => {
      if (Object.prototype.hasOwnProperty.call(req.body || {}, field)) {
        let val = req.body[field];
        // normalize boolean-like fields
        if (field === 'is_function' || field === 'full_page') {
          val = val ? 1 : 0;
          const column = field === 'is_function' ? 'b_function' : 'full_page';
          updates.push(`${column} = ?`);
          values.push(val);
        } else if (field === 'parent_id') {
          updates.push('parent_id = ?');
          values.push(val || null);
        } else {
          updates.push(`${field} = ?`);
          values.push(val);
        }
      }
    });

    if (updates.length === 0) return res.status(400).json({ message: 'No fields to update' });

    values.push(id);
    const sql = `UPDATE atb_menu SET ${updates.join(', ')} WHERE id = ?`;
    const [result] = await pool.query(sql, values);

    // check affectedRows? proceed to fetch
    const [rows] = await pool.query(`
      SELECT id, name, parent_id, order_num, url, icon, remarks, code, full_page, iframe_url, b_function
      FROM atb_menu WHERE id = ?
    `, [id]);

    if (!rows || rows.length === 0) return res.status(404).json({ message: 'Menu not found' });

    const r = rows[0];

    // log update
    createLog(req, { message: `编辑菜单 id=${id}`, tp: 'update', obj_json: JSON.stringify({ id: r.id, name: r.name }) });

    res.json({ menu: {
      id: r.id,
      name: r.name,
      parent_id: r.parent_id,
      order_num: r.order_num,
      url: r.url,
      code: r.code,
      is_function: r.b_function,
      icon: r.icon,
      iframe_url: r.iframe_url,
      full_page: r.full_page,
      remarks: r.remarks,
      children: []
    }});
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// DELETE /api/menus/:id - delete a menu
router.delete('/menus/:id', authMiddleware, async (req, res) => {
  try {
    const id = parseInt(req.params.id, 10);
    if (isNaN(id)) return res.status(400).json({ message: 'Invalid menu id' });

    // ensure menu exists
    const [found] = await pool.query('SELECT id FROM atb_menu WHERE id = ?', [id]);
    if (!found || found.length === 0) return res.status(404).json({ message: 'Menu not found' });

    // prevent deleting if has children
    const [childCountRows] = await pool.query('SELECT COUNT(*) AS cnt FROM atb_menu WHERE parent_id = ?', [id]);
    const cnt = (childCountRows && childCountRows[0] && childCountRows[0].cnt) ? childCountRows[0].cnt : 0;
    if (cnt > 0) return res.status(400).json({ message: 'Cannot delete menu that has child menus' });

    // delete role mappings and the menu
    await pool.query('DELETE FROM atb_menu_role WHERE menu_id = ?', [id]);
    await pool.query('DELETE FROM atb_menu WHERE id = ?', [id]);

    // log deletion
    createLog(req, { message: `删除菜单 id=${id}`, tp: 'delete', obj_json: JSON.stringify({ id }) });

    res.json({ message: 'Deleted' });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

// POST /api/menus/:id - support method override (for clients that cannot send DELETE)
router.post('/menus/:id', authMiddleware, async (req, res, next) => {
  try {
    const override = (req.headers['x-http-method-override'] || (req.body && req.body._method) || '').toString().toUpperCase();
    if (override !== 'DELETE') return next();

    const id = parseInt(req.params.id, 10);
    if (isNaN(id)) return res.status(400).json({ message: 'Invalid menu id' });

    // ensure menu exists
    const [found] = await pool.query('SELECT id FROM atb_menu WHERE id = ?', [id]);
    if (!found || found.length === 0) return res.status(404).json({ message: 'Menu not found' });

    // prevent deleting if has children
    const [childCountRows] = await pool.query('SELECT COUNT(*) AS cnt FROM atb_menu WHERE parent_id = ?', [id]);
    const cnt = (childCountRows && childCountRows[0] && childCountRows[0].cnt) ? childCountRows[0].cnt : 0;
    if (cnt > 0) return res.status(400).json({ message: 'Cannot delete menu that has child menus' });

    // delete role mappings and the menu
    await pool.query('DELETE FROM atb_menu_role WHERE menu_id = ?', [id]);
    await pool.query('DELETE FROM atb_menu WHERE id = ?', [id]);

    // log deletion
    createLog(req, { message: `删除菜单 id=${id}`, tp: 'delete', obj_json: JSON.stringify({ id }) });

    res.json({ message: 'Deleted' });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server error' });
  }
});

module.exports = router;